#pragma once
#include <chrono>
#include <string>
#include <sstream>
#include <functional>
#include "socket_session.h"
#include "packet_parser.h"
#include "const_value.h"
#include "packet_msg_.h"
#include "config.h"
#include "msg.h"


namespace acl
{
namespace mqtt
{
class socket_session_auth
{
public:
	enum status_t
	{
		e_wait_for_connect,
		e_unacceptable_proctocol,
		e_identifier_rejected,
		e_password_auth_failed,
		e_ok,
	};

	socket_session_auth() 
	{

	}
	
	socket_session_auth & bind_socket_session(socket_session && _sock_ss) {

		packet_parser_.bind_connect_msg_handler([this](connect_msg &&msg) {
			stop_recv_ = true;
			return handle_connect_msg(std::move(msg));
		});

		sock_sess_ = std::move(_sock_ss);
		sock_sess_.bind_recv_callback([this](char *data, int len) {
			bool ret = packet_parser_.do_parse(data, len);
			if(ret == false) {
				if(status_ == e_wait_for_connect)
					packet_parse_failed();
			}
			if(stop_recv_ == false)
				sock_sess_.to_recv(0, auto_timeout_);
			return true;
		}).bind_timeout_callback([this](socket_session &sock_ss) {
			(void) sock_ss;
			stop_recv_ = true;
			failed_callback_(std::move(sock_sess_));
			return true;
		}).bind_disconnect_callback([this](socket_session &sock_ss) { 
			(void) sock_ss;
			stop_recv_ = true;
			failed_callback_(std::move(sock_sess_));
			return true;
		});

		status_ = e_wait_for_connect;
		return *this;
	}
	template<class CliSessCallBack>
	socket_session_auth 
		&bind_auth_succeed_callback(CliSessCallBack callback) {
		secceed_callback_ = callback;
		return *this;
	}
	template<class AuthFailedCallBack>
	socket_session_auth &
		bind_auth_failed_callback(AuthFailedCallBack callback) {
		failed_callback_ = callback;
		return *this;
	}
	socket_session_auth & auth_timeout(int seconds) 
	{
		auto_timeout_ = seconds;
		return *this;
	}
	void run() 
	{
		sock_sess_.to_recv(0, auto_timeout_);
	}
	bool is_done ()
	{
		return is_done_;
	}
private:
	void packet_parse_failed() 
	{
		is_done_ = true;
		stop_recv_ = true;
		failed_callback_(std::move(sock_sess_));
	}
	bool handle_connect_msg(connect_msg &&msg) {

		is_done_ = true;
		if(msg.protocol_name_ != MQTT_NAME)
			return false;

		if( (msg.protocol_level_ & 0x7F) != MQTT_PROTOCOL) {
			response_connect_ack_msg(UNACCEPTABLE_PROTOCOL_VERSION);
			status_ = e_unacceptable_proctocol;
			stop_recv_ = true;
			return true;
		}

		if(msg.connect_flags_.clean_session_ == 0 &&
			msg.clientid_.length() == 0) {
			response_connect_ack_msg(IDENRIFIER_REJECTED);
			status_ = e_identifier_rejected;
			stop_recv_ = true;
			return true;
		}

		if(config::get_instance().need_pass_auth()) {
			bool res = false;
			if(msg.connect_flags_.password_flag_) {
				if(msg.connect_flags_.user_name_flag_) {
					res = config::get_instance()
						.get_password_auth()
						.check(msg.user_name_, msg.password_);
				}
				else
					res = config::get_instance()
					.get_password_auth()
					.check(msg.password_);
			}
			if(res == false) {
				response_connect_ack_msg(USERNAME_OR_PASSWORD_ERROR);
				status_ = e_password_auth_failed;
				stop_recv_ = true;
				return true;
			}
		}
		secceed_callback_(std::move(sock_sess_), 
						  std::move(msg),std::move(packet_parser_));
		return true;
	}

	void response_connect_ack_msg(uint8_t return_code) 
	{
		connect_ack_msg msg;
		msg.fixed_header_.control_packet_type_ = e_connect_ack;
		msg.fixed_header_.reserved_ = 0;
		msg.fixed_header_.remaining_length_ = 2;
		msg.return_code_ = return_code;
		msg.session_present_flag_ = 0;

		std::string data = packet_msg(std::move(msg));
		sock_sess_.send(std::move(data));
	}
private:
	status_t status_;
	socket_session sock_sess_;
	packet_parser packet_parser_;
	bool stop_recv_ = false;
	int auto_timeout_ = 0;

	std::function<void(socket_session &&, 
					   connect_msg &&,
					   packet_parser &&)> secceed_callback_;

	std::function<void(socket_session &&)> failed_callback_;
	bool is_done_ = false;
};
}
}
